package cn.qy.kotlin.base

/**
 * 函数
 */
fun main() {
    castFireball()
    castFireball(3)

    castFireball2()
    castFireball2(5)

    // 多参数的函数调用示例
    auraColor(false, 100, true)
    // 如果使用命名值参，就可以不用管值参的顺序
    auraColor(isImmortal = false, healthPoints = 80, isBlessed = true)

    // 如果函数名带有空格则需要引起来
    `users should be signed out when they click logout`()

    // TODO 函数的任务就是抛出异常——换句话说，就是永远别指望它运行成功——返回 Nothing
    /// var todo: Nothing = TODO()

    // 把一个匿名函数作为参数传递给 count 方法
    val numLetters = "hello world".count { letter -> letter == 'l' }
    println(numLetters)

    // 定义一个有 0 个参数的匿名函数, 把匿名函数赋值给变量
    val greetingFunction: () -> String = {
        val currentYear = 2020
        "hello my dear, $currentYear"
    }
    // 匿名函数的调用
    println(greetingFunction)
    println(greetingFunction())

    // 定义一个有 1 个参数的匿名函数
    val greetingFunction2: (String) -> String = { playerName ->
        val currentYear = 2021
        "Welcome to SimVillage, $playerName! (copyright $currentYear)"
    }
    println(greetingFunction2("Guyal"))

    val greetingFunction3 = { playerName: String ->
        val currentYear = 2022
        "Welcome to SimVillage, $playerName! (copyright $currentYear)"
    }
    println(greetingFunction3("Guyal"))

    // 定义只有一个参数的匿名函数时，可以使用 it 关键字来表示参数名, 因此可以删除花括号内的参数名和箭头符号
    val greetingFunction4: (String) -> String = {
        val currentYear = 2023
        "Welcome to SimVillage, $it! (copyright $currentYear)"
    }
    println(greetingFunction4("Guyal"))

    // 定义一个有 2 个参数的匿名函数,因为有了两个参数，所以 it 关键字就不能用了
    val greetingFunction5: (String, Int) -> String = { playerName, numBuildings ->
        val currentYear = 2024
        println("Adding $numBuildings houses")
        "Welcome to SimVillage, $playerName! (copyright $currentYear)"
    }
    println(greetingFunction5("Guyal", 2))

    // Kotlin 的类型推断规则同样也适用函数类型，但为了帮助编译器更准确地推断变量类型，匿名函数的参数名和参数类型必须有。
    val greetingFunction6 = { playerName: String, numBuildings: Int ->
        val currentYear = 2025
        println("Adding $numBuildings houses")
        "Welcome to SimVillage, $playerName! (copyright $currentYear)"
    }
    println(greetingFunction6("Guyal", 2))

    // 匿名函数还能用自定义的函数, 从现在起，我们将把匿名函数改称为 lambda，将它的定义改称为 lambda 表达式，它的返回数据改称为 lambda 结果
    fun runSimulation(playerName: String, greetingFunction: (String, Int) -> String) {
        val numBuildings = (1 until 5).shuffled().first()
        println(greetingFunction(playerName, numBuildings))
    }
    // 函数的调用
    // 如果一个函数的 lambda 参数排在最后，或者是唯一的参数，那么括住 lambda 值参的一对圆括号就可以省略
    runSimulation("jack") { playerName, numBuildings ->
        val currentYear = 2026
        println("Adding $numBuildings houses")
        "Welcome to SimVillage, $playerName! (copyright $currentYear)"
    }

    // 定义一个具名函数
    fun printConstructionCost(numBuildings: Int) {
        val cost = 500
        println("construction cost: ${cost * numBuildings}")
    }
    // 重载一个 runSimulation
    fun runSimulation(playerName: String, costPrinter: (Int) -> Unit, greetingFunction: (String, Int) -> String) {
        val numBuildings = (1 until 5).shuffled().first()
        costPrinter(numBuildings)
        println(greetingFunction(playerName, numBuildings))
    }
    // 传递函数引用
    runSimulation("tom", ::printConstructionCost) { playerName: String, numBuildings: Int ->
        val currentYear = 2027
        println("Adding $numBuildings houses")
        "Welcome to SimVillage, $playerName! (copyright $currentYear)"
    }

    // 函数类型作为返回类型
    fun configureGreetingFunction(): (String) -> String {
        val structureType = "hospitals"
        var numBuildings = 5
        return { playerName: String ->
            val currentYear = 2018
            numBuildings++
            println("Adding $numBuildings $structureType")
            "Welcome to SimVillage, $playerName! (copyright $currentYear)"
        }
    }
    // Kotlin 中的 lambda 就是闭包
    // 优先对待函数，让 Kotlin 语言用起来更灵活。开发者的宝贵时间能花在编程任务中更有价值 的地方，而不是机械地编写样板代码。
    fun runSimulation() {
        val f2 = configureGreetingFunction()
        println(f2("cook"))
        println(f2("cook"))
    }
    runSimulation()
}

/**
 * 参数带有默认值
 */
// 没有返回值的函数在 Kotlin 中，这样的函数叫 Unit 函数
// Unit 表示一个函数不返回任何东西，
// 同时，也能兼容需要和一些类型打交道的泛型函数。无论有无类型，都不是问题，这正是 Kotlin
// 使用 Unit 的原因。
private fun castFireball(numFireballs: Int = 1) {
    println("A glass of Fireball springs into existence. (x$numFireballs)")
}

/**
 * 表达式函数体：如果函数都只有一个表达式语句，可以写成单表达式函数,返回类型、花括号、返回语句都可以省掉。
 */
private fun castFireball2(numFireballs: Int = 1) =
    println("A glass of Fireball springs into existence. (x$numFireballs)")

private fun auraColor(
    isBlessed: Boolean,
    healthPoints: Int,
    isImmortal: Boolean
): String {
    val auraVisible = isBlessed && healthPoints > 50 || isImmortal
    return if (auraVisible) "GREEN" else "NONE"
}

// 如果函数名带有空格则需要引起来
fun `users should be signed out when they click logout`() = println("为了测试函数命名，终于可以不用管函数“小写字母开头，单词以驼峰形式拼接”的命名规则了")
